TI_GDT      equ 0
RPL0        equ 0

SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0

[bits 32]
section .text
global put_char
put_char:
    pushad

    ; 每次调用都确保gs的值为视频段选择子
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    ; 获取光标位置
    ; 先获取光标位置高8位
    ; 需要先将索引写入0x3D4寄存器
    ; 获取光标高8的索引是 0x0e
    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    ; 从0x3d5寄存器读数据
    mov dx, 0x3d5
    in al, dx
    mov ah, al

    ; 获取光标位置低8位
    ; 获取光标低8的索引是0x0f
    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    ; 从0x3d5寄存器读数据
    mov dx, 0x3d5
    in al, dx

    ; 备份到bx中
    mov bx, ax

    ; pushad指令压入备份所有32位寄存器 一共有8个
    ; push all double
    ; 8个32位寄存器是4*8 = 32
    ; 调用本函数入栈4字节段选择子 所以32 + 4 = 36
    mov ecx, [esp + 36]
    ; 由于打印一个字符只有低7位有效，所以只保留低7
    and ecx, 0x0000007f

    ; 如果是退格键
    cmp cl, 0x08
    jz .is_backspace

    cmp cl, 0x0a
    jz .is_nl_line_feed

    cmp cl, 0x0d
    jz .is_carriage_eturn

    jmp .put_other

.is_backspace:
    ; 将上一个字符设置为空格
    dec bx
    shl bx, 1
    mov byte [gs:bx], ' '
    mov byte [gs:bx + 1], 0x07
    shr bx, 1
    jmp .set_cursor

.put_other:
    ; 每个字符由两个字节保存 所以光标位置*2
    shl bx, 1
    mov [gs:bx], cl
    mov byte [gs:bx + 1], 0x07
    shr bx, 1
    ; 跳过我们刚才打印的字符
    inc bx
    ; 25行 80列 即2000 超过2000就该换行了
    cmp bx, 2000
    jl .set_cursor

.is_nl_line_feed:
.is_carriage_eturn:
    mov dx, 0
    mov ax, bx
    mov si, 80
    ; bx存放光标，将光标除以80，dx存放余数，bx-dx就是80的倍数
    ; 即本行的第一个字符光标
    div si
    sub bx, dx
    ; 切换到下一行
    add bx, 80
    cmp bx, 2000
    jl .set_cursor

; 滚屏原理：将1～24行的数据复制到0～23行，第24行用空格填满
.roll_screen:
    cld
    ; 一共有2000个字符 2000-一行字符80=1920
    ; 一个字符2个字节 所以3840字节
    ; 每次运输4个字节 需要3840/4 = 960
    mov ecx, 960
    ; 第1行行首地址
    mov esi, 0xb80a0
    ; 第0行行首地址
    mov edi, 0xb8000
    rep movsd

    mov ecx, 80
    mov ebx, 3840
.clear_last_line:
    mov word [gs:ebx], 0x0720
    add ebx, 2
    loop .clear_last_line
    ; 设置光标为最后一行行首
    mov bx, 1920

.set_cursor:
    ; 先写入高8位光标地址
    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x3d5
    mov al, bh
    out dx, al

    ; 然后写入低8位光标地址
    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x3d5
    mov al, bl
    out dx, al

.put_char_done:
    popad
    ret

global put_str
put_str:
    pushad

    ; pushad指令压入备份所有32位寄存器 一共有8个
    ; push all double
    ; 8个32位寄存器是4*8 = 32
    ; 调用本函数入栈4字节段选择子 所以32 + 4 = 36
    ; bx存放字符串地址
    mov ebx, [esp + 36]
    xor eax, eax
.put_char_loop:
    mov al, [ebx]
    cmp al, 0
    jz .put_str_end
    push eax
    call put_char
    inc ebx
    add esp, 4
    jmp .put_char_loop

.put_str_end:
    popad
    ret

global put_int_x
put_int_x:
    pushad

    ; 获取光标位置
    ; 先获取光标位置高8位
    ; 需要先将索引写入0x3D4寄存器
    ; 获取光标高8的索引是 0x0e
    mov dx, 0x3d4
    mov al, 0x0e
    out dx, al
    ; 从0x3d5寄存器读数据
    mov dx, 0x3d5
    in al, dx
    mov ah, al
    ; 将光标放入dx保存
    mov dx, ax
    
    ; pushad指令压入备份所有32位寄存器 一共有8个
    ; push all double
    ; 8个32位寄存器是4*8 = 32
    ; 调用本函数入栈4字节段选择子 所以32 + 4 = 36
    ; bx存放32位的数字
    mov ebx, [esp + 36]
    ; 32位中有8个16进制数
    mov ecx, 8
    ; 由于是LSB存储模式 所以从高字节开始
.put_last_num_loop:
    mov eax, ebx
    and eax, 0xf0000000
    ; 32 - 4 = 28，将最高4位变成最低4位
    shr eax, 28
    cmp ax, 10
    jae .put_x
    add ax, '0'
    jmp .put_last_num
.put_x:
    sub ax, 10
    add ax, 'A'
.put_last_num:
    push eax
    call put_char
    add esp, 4
    add dx, 2
    shl ebx, 4
    loop .put_last_num_loop
    popad
    ret


global put_int_d
put_int_d:
    pushad

    ; pushad指令压入备份所有32位寄存器 一共有8个
    ; push all double
    ; 8个32位寄存器是4*8 = 32
    ; 调用本函数入栈4字节段选择子 所以32 + 4 = 36
    ; bx存放32位的数字
    mov eax, [esp + 36]
    mov di, 10
    mov si, 0
    mov ecx, 5
    mov edx, eax
    shr edx, 16
    and eax, 0x0000ffff

.get_all_bit:
    div di
    add dx, '0'
    push edx
    inc si
    mov dx, 0
    cmp ax, 0
    jz .print_int_d_from_stack
    jmp .get_all_bit

.print_int_d_from_stack:
    xor ecx, ecx
    mov cx, si
.loop_print_int_d_from_stack:
    call put_char
    add esp, 4
    loop .loop_print_int_d_from_stack

    popad
    ret